package bijection

object BufferableGenerator {
  val pkg = "package com.twitter.bijection"

  /* Example of the code generated:
    implicit def tuple2[A,B](implicit ba: Bufferable[A], bb: Bufferable[B]): Bufferable[(A,B)] =
      new AbstractBufferable[(A,B)] {
        def put(bytebuf: ByteBuffer, tup: (A,B)) = {
          var nextBb = bytebuf
          nextBb = reallocatingPut(nextBb) { ba.put(_, tup._1) }
          nextBb = reallocatingPut(nextBb) { bb.put(_, tup._2) }
          nextBb
        }
        // this should perform better than for comprehension
        def get(bytebuf: ByteBuffer) = attempt(bytebuf) { bytebuf =>
          val (bufa, a) = ba.unsafeGet(bytebuf)
          val (bufb, b) = bb.unsafeGet(bufa)
          (bufb, (a,b))
        }
      }
  */

  val lowerLetters = ('a' to 'z').toIndexedSeq
  val upperLetters = ('A' to 'Z').toIndexedSeq

  def bufferableParam(idx: Int) = "b" + lowerLetters(idx) + ": Bufferable[" + upperLetters(idx) + "]"

  def typeList(cnt: Int) =
    upperLetters.slice(0, cnt) map { _.toString } mkString(",")

  def parensTypeList(cnt: Int) = "(" + typeList(cnt) + ")"

  def reallocatingPut(idx: Int) =
    "nextBb = reallocatingPut(nextBb) { b" + lowerLetters(idx) + ".put(_, tup._" + (idx+1) +") }"

  def bufferGet(idx: Int) = {
    val getFrom = if (idx == 0) "bytebuf" else ("buf" + lowerLetters(idx - 1))
      val lowlet = lowerLetters(idx)
    "val (buf%s, %s) = b%s.unsafeGet(%s)".format(lowlet, lowlet, lowlet, getFrom)
  }

  def bufferableType(idx: Int) = "Bufferable[" + upperLetters(idx) + "]"

  // Here we put it all together:
  def implicitTuple(cnt: Int): String =
    "  implicit def tuple" + cnt + "[" + typeList(cnt) + "](implicit " +
      ((0 until cnt) map { bufferableParam(_) } mkString(", ") ) + "):\n" +
    "    Bufferable[" + parensTypeList(cnt) + "] = new AbstractBufferable[" + parensTypeList(cnt) +"] {\n" +
    "      def put(bytebuf: ByteBuffer, tup: "+ parensTypeList(cnt) +") = {\n" +
    "        var nextBb = bytebuf\n" +
    "        " + ((0 until cnt) map { reallocatingPut(_) }).mkString("","\n        ","\n") +
    "        nextBb\n" +
    "      }\n"+
    "      def get(bytebuf: ByteBuffer) = attempt(bytebuf) { bytebuf =>\n" +
    "        " + ((0 until cnt) map { bufferGet(_) }).mkString("","\n        ","\n") +
    "        val res = " +(0 until cnt).map { lowerLetters(_) }.mkString("(",", ",")") + "\n" +
    "        (buf" + lowerLetters(cnt - 1) +", res)\n" +
    "      }\n" +
    "    }"

  def generate = {
    val b = new StringBuffer
    b.append("// Autogenerated code DO NOT EDIT BY HAND\n")
    b.append(pkg).append("\n")

    b.append("import Bufferable.reallocatingPut\n")
    b.append("import java.nio.ByteBuffer\n")
    b.append("import com.twitter.bijection.Inversion.attempt\n")

    b.append("\ntrait GeneratedTupleBufferable {\n")
    (2 to 22).foreach { cnt => b.append(implicitTuple(cnt)).append("\n") }
    b.append("}")

    b.toString
  }
}
